Implementation: The Macros:

 

File pseudoPOD.h has the macro definitions that eliminate almost all of the typing required for pseudoPODs (www.dlmowery.com/pseudoPOD/pseudoPOD.h).

Macro ppod6() is the workhorse macro - it creates most of the nested class, then calls one of three other macros that creates the functions that handle up to 12 additional operators (++ et al).  Its only drawback is that it has 6 arguments, 2 of which are rarely used, and 1 of which can usually be deduced from the first argument.  To reduce coder labor I've added other macros that require fewer arguments than ppod6(), but produce the same result because they create code by calling ppod6() after filling in the extra arguments.  You've already seen these 3 argument macros in the instructions on how to create pseudoPODs, and I'll describe their implementation later.

 

ppod6() has 6 arguments:

1) ppod_enclosure_name is the name of the enclosing class, e.g. fred_c.

2) podtype is the type of the pseudoPOD, e.g. int, char, long, int*, and so forth.

3) podname is the name of the pseudoPOD variable, e.g. int1.

4) podoperators specifies what additional operators to support, and its value can usually be deduced from podtype.

"operators_all" creates code that supports all 12 operators (++, --, +=, -+, *=, /=, %=, &=, ^=, |=, <<=, and >>=).  Both prefix and postfix versions of ++ and -- are supported.

"operators_float" creates code for floats and doubles, supporting operators ++, --, +=, -=, *= and /=.

"operators_pointer" is for pointers, creates code only for ++, --, += and -=, and for the last two operators assumes that argument "in" is an integer, since you usually want to add an integer to the pointer, not add another pointer to the pointer.

5) pod_private_if_readonly is normally "" (blank).  If it is "private:" then the pseudoPOD will be readonly for code outside the enclosing class.

6) pod_private_if_writeonly is normally "" (blank).  If it is "private:" then the pseudoPOD will be writeonly for code outside the enclosing class.

 

In file pseudoPOD.h the comments following the definitions of ppod6() and ppod_operators_all() show a typical macro expansion step by step, q.v. 

When the compiler encounters the macro call

ppod6(fred_c, int, int1, operators_all,,);

1) it makes a copy of the code defined in ppod6(), then substitutes the arguments specified in the macro call for the dummy arguments specified in the macro definition.  For this example, it substitutes:

fred_c

for

ppod_enclosure_name

int

for

podtype

int1

for

podname

operators_all

for

podoperators

"" (blank)

for

pod_private_if_readonly

"" (blank)

for

pod_private_if_writeonly

2) Wherever the token pasting operator ## appears, the compiler deletes the ##, creating new words.  For example:

podname##nest

->

int1##_nest

->

int1_nest

get_##podname()

->

get_##int1()

->

get_int1()

set_##podname(in)

->

set_##int1(in)

->

set_int1(in)

ppod_##podoperators(podtype, podname)->

ppod_##operators_all(int,int1)->

                           ppod_operators_all(int,int1)

podtype podname##_value;->

        int int1##_value;->

                           int int1_value;

3) The compiler sees the macro call ppod_operators_all(int,int1), and repeats the preceding steps, copying the text defined in ppod_operators_all(), substituting int/int1 for podtype/podname, and deleting ## to form new names.

4) The result is code similar to the raw code we saw earlier in class fred_c that handles the conversion of POD style calls (  fred.int3 = 35;   ) to familiar get() & set() calls (  fred.set_int3(35);  ).

 

ppod6() Variations:

Floats & Doubles:

Most data types use all 12 additional operators, but floats and doubles normally don't work with logical operators, nor with %=, so for floats and doubles I've deleted the support for these operators.  When creating a float or double using macro ppod6() use "operators_float" for argument "pod_operators", which causes the compiler to call macro ppod_operators_float() rather than ppod_operators_all().  The 3 argument forms of the ppod() macro handle this variation automatically.

Pointers:

Pointers are also a little different.  They support only 4 operators (++, --, +=, -=).  Further, with integers or floats, when you use the += operator you're normally adding two integers or two floats together, but with pointers you normally want to add an integer to a pointer, so for the pointers += and -= operators we have to change the type of argument "in" from pointer (e.g.  operator+=(int* in) ) to integer (e.g. operator+=(int in) ).  Consequently pointers also have their own macro to create their extra operators, and when defining a pointer pseudoPOD using ppod6() specify "operators_pointer" rather than "operators_all".  The 3 argument forms of the ppod() macro can't handle this automatically, so when creating pointers with 3 argument macros you should use ppod_p() rather than ppod().

Arrays:

Arrays require an additional argument to specify the number of elements in the array, so have their own set of macros, which I'll describe later.

 

3 Argument Macros For ppod6():

The  3 argument forms of ppod6() save the coder time by sparing him the trouble of specifying the last 3 arguments to ppod6().  The form of the macro is:

ppod(ppod_enclosure_name, podtype, podname);

where the 3 arguments are the same as defined earlier for ppod6().  This macro works for integers (including long, bool, char, unsigned, etc), floats, and doubles.

ppod() deduces what the 4th argument (podoperators) should be by examining the 2nd argument (podtype).  If podtype is one of the varieties of integers, then the 4th argument should be "operators_all", but if podtype is float or double, then the 4th argument should be "operators_float".

ppod() first calls ppod6, filling in "first_##podtype##_last" for the 4th argument (podoperators).  ppod6() copies this argument to "ppod_##podoperators", producing "ppod_##first_##podtype##_last", which the rest of the ppod() 3 argument code converts to either "ppod_operators_all" or "ppod_operators_float".

Suppose the coder specified:

ppod(fred_c, int, name);

so that podtype = "int" and podname = "name".  ppod() calls ppod6() with the 4th argument (podoperators) set to "first_##int##_last", and the 5th and 6th arguments blank, i.e.:

ppod6(fred_c, int, name, first_##int##_last, , );

ppod6() copies the 4th argument to the place where it calls one of the pod_operators macros, giving:

"ppod_##first_##int##_last(int, name)"

The compiler deletes ##, producing:

"ppod_first_int_last(int, name)"

The code supporting the 3 argument ppod() macro includes the line:

#define ppod_first_int_last        ppod_operators_all

which converts "ppod_first_int_last(int, name)" to:

"ppod_operators_all(int, name)"

which completes the conversion of argument podtype to argument podoperators, by selecting the additional operators that should be used with an integer pseudoPOD.

For other one word data types (long, short, char, bool, float, double, et al) the conversion works similarly. 

For two word data types, such as "unsigned char", it's a little different, because the space between the two words produces two words rather than one.  In this case the code supporting the 3 argument ppod() macro converts the first word to nothing (i.e. "" or blank), and the second word to either "ppod_operators_all" or "ppod_operators_float".  For example:

ppod(fred_c, unsigned char, name);

ppod6(fred_c, unsigned char, name, first_##unsigned char##_last, , );

ppod_##first_##unsigned char##_last(unsigned char, name)

ppod_first_unsigned char_last(unsigned char, name)

#define ppod_first_unsigned  (from ppod()'s support code)

                   char_last(unsigned char, name)

#define char_last ppod_operators_all(from ppod()'s support code)

          ppod_operators_all(unsigned char, name)

For three word data types, such as "unsigned long int", macros cannot convert podtype to the 4th argument because there's no way of attaching another word to the middle word "long", so no way of reducing it to nothing, and the compiler reports an error when it sees it.  Consequently the coder must either use "unsigned long" rather than "unsigned long int", or use the 6 argument macro ppod6().

For pointers, such as int*, there's also problem in that the compiler treats * as a special character rather than as part of a token, so the compiler refuses to convert, for example, "ppod_first_int*_last" to "ppod_operators_pointer".  Consequently, for pointers the coder has to prod the compiler by calling ppod_p() rather than ppod().

I expect read only and write only pseudoPODs will be used less often than normal ones, so left their arguments out of the ppod() call to reduce coding errors (  it's easier to correctly type ppod(fred_c, int, int1); than ppod(fred_c, int, int1, , );   ).  Consequently read only and write only pseudoPODs have their own macro calls ppod_ro() and ppod_wo().  Read only and write only pointers have macro calls ppod_p_ro() and ppod_p_wo().

 

Array Macros:

Arrays are sufficiently different that they have their own set of macros, which are similar to those described earlier for non-array data types.  In particular:

ppod7_array()

~ ppdo6()

ppod7_operators_all()

~ ppod_operators_all()

ppod7_operators_float()

~ ppod_operators_float()

ppod7_operators_pointer()

~ ppod_operators_pointer()

ppod_array()

~ ppod()

ppod_array_p()

~ ppod_p()

ppod_array_ro()

~ ppod_ro()

ppod_array_wo()

~ ppod_wo()

ppod_array_p_ro()

~ ppod_p_ro()

ppod_array_p_wo()

~ ppod_p_wo()

 

ppod7_array() is the workhorse macro that corresponds to ppod6() described earlier. 

Podlen:

ppod7_array() has one additional argument, podlen, that specifies how many elements the array should have. 

Array Index Calculation:

ppod7_array() calculates the index of the array by comparing the "this" pointer of the element that the code is reading or writing, to the "this" pointer of the first element in the array.  Subtracting the address of array[0] ( &(ep->podname[0]) ) from the address of array[N], the element the code is reading or writing ( this ) gives the index N of the element being read or written (formally, it gives the difference in bytes between the two addresses, which then must be divided by the size of the element in bytes to get index N, but the division is handled invisibly by the compiler, so doesn't appear in my code).  The calculated array index is passed on to the familiar get() & set() functions.

Extra operator=() Function Required:

ppod7_array() has an extra function to support some chained operations, such as:

fred.intarr3[1] = fred.intarr3[2] = fred.intarr3[3] = 444;

that was not needed for non-array types, such as:

fred.int3 = fred2.int3b = fred2.int3b = 444;

I don't understand why the compiler requires it for a data type that is an element of an array, but doesn't require it for the same data type when it is not part of an array, but both variants pass their tests. The extra operator=() function is:

inline podname##_nest & operator=(podname##_nest &in) {\

  ep->set_##podname(this-&(ep->podname[0]),in); return(*this);} \

which is identical to the original operator=() function that both array and non-array types share, namely:

inline podname##_nest & operator=(podtype in) {\

  ep->set_##podname(this-&(ep->podname[0]),in); return(*this);} \

except that the common function handles POD data type such as int, while the extra function handles the pseudoPOD type such as int1_nest.  Apparently the compiler is willing, in chained operations, to implicitly convert a pseudoPOD to a POD if it is solitary, but unwilling to convert the same thing if it's an element of an array.